package cn.seocoo.platform.common.pay.wechat;

import cn.seocoo.platform.common.pay.alipay.AliPayUtils;
import cn.seocoo.platform.common.utils.DateUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jpay.ext.kit.PaymentKit;
import com.jpay.weixin.api.WxPayApi;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.text.DecimalFormat;
import java.util.*;

@Component
public class WechatPayUtils {

    private static final Logger logger = LoggerFactory.getLogger(AliPayUtils.class);

    /**
     * 微信开放平台 appId (在微信开放平台获取)
     */
    private static String wechatAppId;

    @Value("${wechat.appId}")
    public void setWechatAppId(String wechatAppId) {
        WechatPayUtils.wechatAppId = wechatAppId;
    }

    /**
     * 小程序 appId
     */
    private static String appletsID;

    @Value("${wechat.appletsID}")
    public void setAppletsID(String appletsID) {
        WechatPayUtils.appletsID = appletsID;
    }

    /**
     * 小程序 key
     */
    private static String appletsSecretKey;

    @Value("${wechat.appletsSecretKey}")
    public void setAppletsSecretKey(String appletsSecretKey) {
        WechatPayUtils.appletsSecretKey = appletsSecretKey;
    }

    /**
     * 微信支付商户ID
     */
    private static String wechatMchId;

    @Value("${wechat.mchId}")
    public void setWechatMchId(String wechatMchId) {
        WechatPayUtils.wechatMchId = wechatMchId;
    }

    /**
     * 微信支付商户密钥key
     */
    private static String wechatPartnerKey;

    @Value("${wechat.partnerKey}")
    public void setWechatPartnerKey(String wechatPartnerKey) {
        WechatPayUtils.wechatPartnerKey = wechatPartnerKey;
    }

    /**
     * 用于获取openId
     */
    private static String grantType;

    @Value("${wechat.session.grantType}")
    public void setGrantType(String grantType) {
        WechatPayUtils.grantType = grantType;
    }

    /**
     * 微信支付回调接口地址
     */
    private static String wechatNotifyUrl;

    @Value("${wechat.notifyUrl}")
    public void setWechatNotifyUrl(String wechatNotifyUrl) {
        WechatPayUtils.wechatNotifyUrl = wechatNotifyUrl;
    }


    /**
     * 支付来源 APP
     */
    public static final String PAY_SOURCE_APP = "1";

    /**
     * 支付来源 PC
     */
    public static final String PAY_SOURCE_PC = "2";

    private static final String BODY = "画图之家-订单支付";


    /**
     * 微信APP 支付
     *
     * @author WuJian
     * @date: 2019/11/22 17:26
     */
    public static String wechatPayForAPP(String paySource, String orderNumber, String totalAmount, Map<String, Object> orderMap) {
        logger.warn("-------------------->微信APP支付 统一支付下单开始<-------------------------");
        //创建 时间戳
        String timeStamp = Long.valueOf(System.currentTimeMillis()).toString();
        //生成32位随机数
        String nonceStr = generateNonceStr();
        Map<String, String> restmap;
        Map<String, String> parm = new HashMap<>();
        parm.put("appid", wechatAppId);
        parm.put("mch_id", wechatMchId);
        parm.put("device_info", "WEB");
        //订单的有效时间:10分钟
        parm.put("time_expire", DateUtils.getOrderExpireTime(10 * 60 * 1000L));
        parm.put("body", BODY);
        parm.put("nonce_str", nonceStr);
        //-----附加数据开始:attach参数,在查询API和支付通知中原样返回，可作为自定义参数使用。
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("orderMsg", orderMap);
        parm.put("attach", JSON.toJSONString(map));
        logger.warn("微信APP下单  attach长度是--------->" + parm.get("attach").length());
        //-----附件参数结束
        parm.put("out_trade_no", orderNumber);
        logger.warn("微信APP下单  金额是--------->" + totalAmount + "元");
        BigDecimal price = new BigDecimal("0.01");
        //BigDecimal price = new BigDecimal("" + totalAmount + "");
        BigDecimal beishu = new BigDecimal("100");
        //支付金额 **金额不能有小数点,单位是分!!**
        BigDecimal priceFee = price.multiply(beishu);
        parm.put("total_fee", priceFee.toBigInteger().toString());
        //获取客户端的ip地址
        //获取本机的ip地址
        InetAddress addr = null;
        try {
            addr = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        //这个暂时没有值，但微信要求必传
        parm.put("spbill_create_ip", addr.getHostAddress());
        // 微信服务器异步通知支付结果地址
        parm.put("notify_url", wechatNotifyUrl);
        // 判断支付来源交易类型
        if (PAY_SOURCE_APP.equals(paySource)) {
            parm.put("trade_type", String.valueOf(WxPayApi.TradeType.APP));
        }
        //获取验签秘钥key
/*        String signkeyXml = WxPayApi.getsignkey(wechatMchId,wechatPartnerKey);
        Map<String, String> signKeyMap = PaymentKit.xmlToMap(signkeyXml);
        String signKey = signKeyMap.get("sandbox_signkey");
        logger.warn("获取验签秘钥---------------------->: " + signKey);*/
        //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
        parm.put("sign", PaymentKit.createSign(parm, wechatPartnerKey));
        logger.warn("微信APP支付 统一下单发送的数据: " + parm.toString());
        logger.warn(PaymentKit.toXml(parm));
        //调用统一下单接口,向微信api发送数据
        String restxml = WxPayApi.pushOrder(false, parm);
        restmap = PaymentKit.xmlToMap(restxml);
        logger.warn("微信APP支付 统一下单接受返回的结果: " + restmap);
        //返回状态码
        String return_code = restmap.get("return_code");
        String return_msg = restmap.get("return_msg");
        if ("SUCCESS".equals(return_code)) {
            Map<String, String> payMap = new HashMap<String, String>();
            payMap.put("appid", wechatAppId);
            payMap.put("partnerid", wechatMchId);
            //微信中预支付的标识
            payMap.put("prepayid", restmap.get("prepay_id"));
            payMap.put("package", "Sign=WXPay");
            payMap.put("noncestr", nonceStr);
            payMap.put("timestamp", timeStamp);
            //再次签名，验签
            //沙箱环境：需将wechatPartnerKey替换为signKey
            payMap.put("sign", PaymentKit.createSign(payMap, wechatPartnerKey));
            logger.warn("-------------------->微信APP支付 统一支付下单结束<-------------------------");
            return payMap.toString();
        } else {
            logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + ">>>>>>>>>>>>>>>>>>>>" + return_msg);
            return getMsgByCode(return_code, return_msg);
        }
    }


    /**
     * 微信PC支付
     *
     * @author WuJian
     * @date: 2019/11/22 17:26
     */
    public static String wechatPayForPC(String paySource, String orderNumber, String totalAmount, Map<String, Object> orderMap) throws Exception {
        logger.warn("-------------------->微信PC支付 统一支付下单开始<-------------------------");
        //生成32位随机数
        String nonceStr = generateNonceStr();
        Map<String, String> restmap;
        Map<String, String> parm = new HashMap<>();
        parm.put("appid", wechatAppId);
        parm.put("mch_id", wechatMchId);
        parm.put("device_info", "WEB");
        //订单的有效时间:10分钟
        parm.put("time_expire", DateUtils.getOrderExpireTime(10 * 60 * 1000L));
        parm.put("body", BODY);
        parm.put("nonce_str", nonceStr);
        //-----附加数据开始:attach参数,在查询API和支付通知中原样返回，可作为自定义参数使用。
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("orderMsg", orderMap);
        parm.put("attach", JSON.toJSONString(map));
        logger.warn("微信PC下单  attach长度是--------->" + parm.get("attach").length());
        //-----附件参数结束
        parm.put("out_trade_no", orderNumber);
        logger.warn("微信PC下单  金额是--------->" + totalAmount + "元");
        //BigDecimal price = new BigDecimal("" + totalAmount + "");
        BigDecimal price = new BigDecimal("0.01");
        BigDecimal beishu = new BigDecimal("100");
        //支付金额 **金额不能有小数点,单位是分!!**
        BigDecimal priceFee = price.multiply(beishu);
        parm.put("total_fee", priceFee.toBigInteger().toString());
        //获取客户端的ip地址
        //获取本机的ip地址
        InetAddress addr = null;
        try {
            addr = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        //这个暂时没有值，但微信要求必传
        parm.put("spbill_create_ip", addr.getHostAddress());
        // 微信服务器异步通知支付结果地址
        parm.put("notify_url", wechatNotifyUrl);
        // 判断支付来源交易类型
        if (PAY_SOURCE_PC.equals(paySource)) {
            parm.put("trade_type", String.valueOf(WxPayApi.TradeType.NATIVE));
        }
        //获取验签秘钥key
/*        String signkeyXml = WxPayApi.getsignkey(wechatMchId,wechatPartnerKey);
        Map<String, String> signKeyMap = PaymentKit.xmlToMap(signkeyXml);
        String signKey = signKeyMap.get("sandbox_signkey");
        logger.warn("获取验签秘钥---------------------->: " + signKey);*/
        parm.put("sign", PaymentKit.createSign(parm, wechatPartnerKey));
        logger.warn("微信PC支付 统一下单发送的数据: " + parm.toString());
        //调用统一下单接口,向微信api发送数据
        String restxml = WxPayApi.pushOrder(false, parm);
        restmap = PaymentKit.xmlToMap(restxml);
        logger.warn("微信PC支付 统一下单接受返回的结果: " + restmap);
        String return_msg = restmap.get("return_msg");
        String return_code = restmap.get("return_code");
        String result_code = restmap.get("result_code");
        //根据微信文档return_code 和result_code都为SUCCESS的时候才会返回code_url
        if ("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)) {
            String code_url = restmap.get("code_url");
            logger.warn("-------------------->微信PC支付 统一支付下单结束<-------------------------");
            return code_url;
        } else {
            String err_code = restmap.get("err_code");
            if (err_code == null) {
                err_code = "";
            }
            logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + ">>>>>>>>>>>>>>>>>>>>" + return_msg);
            return getMsgByCode(err_code, return_msg);
        }
    }

    /**
     * 申请退款
     *
     * @param certPath 证书文件目录
     * @param certPass 证书密码
     * @author WuJian
     * @date: 2019/11/22 17:05
     */
    public static String wechatRefund(String orderNo, int totalAmount, String certPath, String certPass) throws Exception {
        logger.warn("-------------------->微信申请退款开始<-------------------------");
        //生成32位随机数
        String nonceStr = generateNonceStr();
        Map<String, String> restmap = null;
        Map<String, String> parm = new HashMap<String, String>();
        //数据库保存的金额单位是分，支付宝支付金额是元 float计算有精度问题
        DecimalFormat fnum = new DecimalFormat("##0.00");
        parm.put("appid", wechatAppId);
        parm.put("mch_id", wechatMchId);
        parm.put("nonce_str", nonceStr);
        parm.put("out_trade_no", orderNo);
        parm.put("out_refund_no", orderNo);
        parm.put("total_fee", String.valueOf(totalAmount));
        parm.put("refund_fee", String.valueOf(totalAmount));
        parm.put("refund_desc", "取消订单");
        // 微信服务器异步通知支付结果地址
        parm.put("notify_url", wechatNotifyUrl);
        parm.put("sign", PaymentKit.createSign(parm, wechatPartnerKey));
        logger.warn("微信申请退款 发送的数据: " + parm.toString());
        String restxml = WxPayApi.orderRefund(false, parm, certPath, certPass);
        restmap = PaymentKit.xmlToMap(restxml);
        logger.warn("微信申请退款 返回报文: " + restmap);
        JSONObject masp = JSONObject.parseObject(JSON.toJSONString(restmap));
        logger.warn("-------------------->微信申请退款 结束<-------------------------");
        return masp.toString();
    }


    /**
     * 微信支付订单查询
     *
     * @param orderNumber 商户系统内部订单号
     * @param payType     支付方式（2:微信; 3:小程序）
     * @author WuJian
     * @date: 2019/11/22 17:25
     */
    public static Map<String, String> searchOrderInfo(String orderNumber, String payType) {
        logger.warn("-------------------->微信支付查询订单信息 开始<-------------------------");
        Map map = new HashMap();
        //生成32位随机数
        String nonceStr = generateNonceStr();
        Map<String, String> restmap = null;
        Map<String, String> parm = new HashMap<String, String>();
        if ("2".equals(payType)) {
            parm.put("appid", wechatAppId);
        } else if ("3".equals(payType)) {
            parm.put("appid", appletsID);
        }
        parm.put("mch_id", wechatMchId);
        parm.put("nonce_str", nonceStr);
        parm.put("out_trade_no", orderNumber);
        //MD5运算生成签名
        String sign = PaymentKit.createSign(parm, wechatPartnerKey);
        parm.put("sign", sign);
        logger.warn("微信支付查询订单信息 发送的数据: " + parm.toString());
        String restxml = WxPayApi.orderQuery(false, parm);
        restmap = PaymentKit.xmlToMap(restxml);
        logger.warn("微信支付查询订单信息 返回的数据: " + restmap.toString());
        String return_code = restmap.get("return_code");
        String result_code = restmap.get("result_code");
        logger.warn("-------------------->微信支付查询订单信息 结束<-------------------------");
        if ("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)) {
            map.put("tradeState", restmap.get("trade_state"));
        }
        return map;
    }

    /**
     * 小程序微信支付
     *
     * @param weixinCode 接受参数(code)
     * @return
     * @author duanpj
     */
    public static String appletPay(String weixinCode, String orderNumber, String totalAmount, Map<String, Object> orderMsg) {
        logger.warn("-------------------->微信小程序统一支付下单开始<-------------------------");
        String openId = getOpenId(weixinCode);
        String return_msg = "统一订单失败";
        try {
            logger.warn("小程序下单  金额是--------->" + totalAmount + "元");
            //BigDecimal price = new BigDecimal(totalAmount);
            BigDecimal price = new BigDecimal("0.01");
            BigDecimal beishu = new BigDecimal("100");
            //支付金额 **金额不能有小数点,单位是分!!**
            BigDecimal priceFee = price.multiply(beishu);
            //商家订单号
            String orderNo = orderNumber;
            //创建 时间戳
            String timeStamp = Long.valueOf(System.currentTimeMillis()).toString();
            //生成32位随机数
            String nonceStr = generateNonceStr();
            //获取客户端的ip地址
            //获取本机的ip地址
            InetAddress addr = null;
            try {
                addr = InetAddress.getLocalHost();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
            String spbill_create_ip = addr.getHostAddress();
            //创建hashmap(用户获得签名)
            SortedMap<String, String> paraMap = new TreeMap<String, String>();
            //设置请求参数(小程序ID)
            paraMap.put("appid", appletsID);
            //设置请求参数(商户号)
            paraMap.put("mch_id", wechatMchId);
            //设置请求参数(随机字符串)
            paraMap.put("nonce_str", nonceStr);
            //附加数据开始:attach参数,在查询API和支付通知中原样返回，可作为自定义参数使用。
            Map<String, Object> mmap = new HashMap<String, Object>();
            mmap.put("orderMsg", orderMsg);
            paraMap.put("attach", JSON.toJSONString(mmap));
            logger.warn("小程序支付下单  attach长度是--------->" + paraMap.get("attach").length());
            //设置请求参数(商品描述)
            paraMap.put("body", BODY);
            //设置请求参数(商户订单号)
            paraMap.put("out_trade_no", orderNo);
            //设置请求参数(总金额)
            paraMap.put("total_fee", priceFee.toBigInteger().toString());
            //设置请求参数(终端IP) 如果是springmvc,或者能获取到request的servlet,用下面这种
            paraMap.put("spbill_create_ip", spbill_create_ip);
            //设置请求参数(通知地址)
            paraMap.put("notify_url", wechatNotifyUrl);
            //设置请求参数(交易类型)
            paraMap.put("trade_type", String.valueOf(WxPayApi.TradeType.JSAPI));
            //设置请求参数(openid)(在接口文档中 该参数 是否必填项 但是一定要注意 如果交易类型设置成'JSAPI'则必须传入openid)
            paraMap.put("openid", openId);
            //MD5运算生成签名，这里是第一次签名，用于调用统一下单接口
            String sign = PaymentKit.createSign(paraMap, wechatPartnerKey);
            paraMap.put("sign", sign);
            //统一下单,向微信api发送数据
            logger.warn("微信小程序统一下单发送的数据: " + paraMap.toString());
            String xmlResult = WxPayApi.pushOrder(false, paraMap);
            //转成xml
            Map<String, String> map = PaymentKit.xmlToMap(xmlResult);
            logger.warn("微信小程序统一下单接受返回的结果: " + map);
            //返回状态码
            String return_code = map.get("return_code");
            return_msg = return_msg + ", " + map.get("return_msg");
            //返回给小程序端需要的参数
            Map<String, String> returnMap = new HashMap<String, String>();
            if ("SUCCESS".equals(return_code)) {
                //返回的预付单信息
                returnMap.put("appId", appletsID);
                returnMap.put("nonceStr", nonceStr);
                String prepay_id = map.get("prepay_id");
                returnMap.put("package", "prepay_id=" + prepay_id);
                returnMap.put("signType", "MD5");
                //这边要将返回的时间戳转化成字符串，不然小程序端调用wx.requestPayment方法会报签名错误
                returnMap.put("timeStamp", timeStamp);
                //拼接签名需要的参数
                //再次签名，这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = PaymentKit.createSign(returnMap, wechatPartnerKey).toUpperCase();
                returnMap.put("paySign", paySign);
                returnMap.put("orderId", orderNo);
                logger.warn("prepay_id-------------------->" + prepay_id);
                logger.warn("-------------------->微信小程序统一支付下单结束<-------------------------");
                return returnMap.toString();
            } else {
                logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + ">>>" + return_msg);
                return getMsgByCode(return_code, return_msg);
            }
        } catch (Exception e) {
            logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + "发生的异常是: ", e);
            return "微信支付下单失败,请稍后再试";
        }
    }

    /**
     * 小程序获取微信openId
     *
     * @param code
     * @return
     * @author duanpj
     */
    private static String getOpenId(String code) {
        String openid = "";
        if (code == null || code.length() == 0) {
            return openid;
        }
        //微信的接口（小程序 appId和key）
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appletsID +
                "&secret=" + appletsSecretKey + "&js_code=" + code + "&grant_type=" + grantType;
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
        if (responseEntity != null && responseEntity.getStatusCode() == HttpStatus.OK) {
            String sessionData = responseEntity.getBody();
            logger.warn("获取openId返回报文----------------->" + sessionData);
            net.sf.json.JSONObject json = net.sf.json.JSONObject.fromObject(sessionData);
            openid = json.get("openid").toString();
            return openid;
        }
        return openid;
    }

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }

    /**
     * 判断返回的return_code,给前端相应的提示
     *
     * @param return_code
     * @return
     * @author zgd
     * @time 2018年7月9日17:53:13
     */
    private static String getMsgByCode(String return_code, String return_msg) {
        switch (return_code) {
            case "NOTENOUGH":
                return "您的账户余额不足";
            case "ORDERPAID":
                return "该订单已支付完成,请勿重复支付";
            case "ORDERCLOSED":
                return "当前订单已关闭，请重新下单";
            case "SYSTEMERROR":
                return "系统超时，请重新支付";
            case "OUT_TRADE_NO_USED":
                return "请勿重复提交该订单";
            default:
                return return_msg;
        }
    }

}
